const net = require('net')
const Buffer = require('buffer').Buffer
const fs = require('fs')

const tracker = require('./tracker')
const messageBuilder = require('./messageBuilder')
const Pieces = require('./class/Pieces')
const Queue = require('./class/Queue')

let pieces
let saveFile

const isHandshake = (msg) => {
  return msg.length === msg.readUInt8(0) + 49 &&
    msg.toString('utf8', 1, 20) === 'BitTorrent protocol'
}
const requestPiece = (socket, queue) => {
  if (queue.isChoked()) {
    return
  }

  while (queue.len()) {
    const block = queue.unqueue()
    if (pieces.need(block)) {
      socket.write(messageBuilder.request(block))
      pieces.request(block)
      break
    }
  }
}
const handleChoke = (socket) => {
  socket.end()
}
const handleUnchoke = (socket, queue) => {
  queue.unchoke()
  requestPiece(socket, queue)
}
const handleHave = (payload, socket, queue) => {
  const pieceIndex = payload.readUInt32BE(0)
  queue.enqueue(pieceIndex)
  if (queue.len() === 1) {
    requestPiece(socket, queue)
  }
}
const handleBitfield = (payload, socket, queue) => {
  const queueEmpty = queue.len() === 0

  payload.forEach((byte, i) => {
    for (let j = 0; j < 8; j++) {
      const flag = byte & 1
      if (flag) {
        queue.enqueue(i * 8 + 7 - j)
        byte >>= 2
      }
    }
  })
  if (queueEmpty) {
    requestPiece(socket, queue)
  }
}
const handlePiece = (payload, socket, queue, torrent) => {
  pieces.receive(payload)
  console.log(`index: ${payload.index}, begin: ${payload.begin}, length: ${payload.block.length}`)
  pieces.progress()
  console.log('--------------------')

  // write file
  const offset = payload.index * torrent.info['piece length'] + payload.begin
  fs.writeSync(saveFile, payload.block, 0, payload.block.length, offset, () => { })

  if (pieces.finished()) {
    socket.end()
    fs.closeSync(saveFile)
    console.log('Download finished!')
    process.exit()
  } else {
    requestPiece(socket, queue)
  }
}
const handleMsg = (msg, socket, queue, torrent) => {
  if (isHandshake(msg)) {
    socket.write(messageBuilder.interested())
  } else {
    const { size, id, payload } = messageBuilder.extractMsg(msg)

    if (id === 0) handleChoke(socket)
    if (id === 1) handleUnchoke(socket, queue)
    if (id === 4) handleHave(payload, socket, queue)
    if (id === 5) handleBitfield(payload, socket, queue)
    if (id === 7) handlePiece(payload, socket, queue, torrent)
    if (id < 0 || id > 7) {
      console.log('else')
    }
  }
}

const download = (peer, torrent) => {
  let savedBuf = Buffer.alloc(0)
  let handshake = true

  const queue = new Queue(torrent)

  const [ip, port] = peer.split(':')
  if (ip === '0.0.0.0') {
    return
  }
  // console.log(`ip: ${ip}, port: ${port}.`)

  const socket = net.Socket()
  socket.on('error', (err) => {
    console.log(err)
  })
  socket.on('data', recvBuf => {
    // msgLen calculates the length of a whole message
    // The handshake message is (49 + len(pstr)) bytes long.
    // A message starts with a length indicator. It’s a 32-bit integer.
    const msgLen = () => handshake ? savedBuf.readUInt8(0) + 49 : savedBuf.readInt32BE(0) + 4
    savedBuf = Buffer.concat([savedBuf, recvBuf])

    while (savedBuf.length >= 4 && savedBuf.length >= msgLen()) {
      handleMsg(savedBuf.subarray(0, msgLen()), socket, queue, torrent)
      savedBuf = savedBuf.subarray(msgLen())

      handshake = false
    }
  })
  socket.connect(port, ip, () => {
    socket.write(messageBuilder.handsShake(torrent))
  })
}

module.exports = {
  start (torrent, savePath) {
    pieces = new Pieces(torrent)
    saveFile = fs.openSync(savePath, 'w')

    tracker.getPeers(torrent, peers => {
      peers.forEach(peer => download(peer, torrent))
    })
  }
}